---------------Math Shop---------------
A 4am crack                  2016-06-15
---------------------------------------

Name: Math Shop
Version: 1986-10-27 (most recent file
  datestamp in ProDOS disk catalog)
Genre: educational
Year: 1986
Credits:
  designed and programmed: Cary Hammer
  contributing designer: Alice Wyman
Publisher: Scholastic, Inc.
Platform: Apple ][+ or later (64K)
Media: single-sided 5.25-inch floppy
OS: ProDOS 1.0
Previous cracks: none (of this version)
Similar cracks:
  #407 Writer Rabbit 1.2

I previously cracked an earlier version
of "Math Shop" dated 1986-01-28 (crack
no. 146). That version used an entirely
different copy protection -- generic
bit-slip protection, or what I've been
calling the "E7 bitstream." It was not
unusual for companies to release minor
updates of popular software with bug
fixes; they would often update the copy
protection at the same time.

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy loads ProDOS then
  reboots

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing immediately suspicious

Disk Fixer
  T00 -> looks like ProDOS bootloader
         and ProDOS disk catalog

Why didn't any of my copies work?
  Probably a nibble check in the
  startup program

Next steps:

  1. Search for the error condition
     (maybe we'll get lucky!)
  2. Disable protection check that
     leads to the error condition
  3. There is no step 3 (I hope)

                   ~

               Chapter 1
        Jump, Jump For My Love


[S6,D1=non-working copy]

Turning to my trusty Disk Fixer sector
editor, I search my non-working copy
for "4C 00 C6" ("JMP $C600", to reboot
from slot 6).

                 --v--

------------- DISK SEARCH -------------

$04/$07-$37   $10/$09-$E7

             PRESS [RETURN]

                 --^--

Two matches. T04,S07 seems like a dead
end. T10,S09, on the other hand,
contains a veritable plethora of red
flags. A few of them that stand out
immediately:

- a JSR to a subroutine, then a
  conditional branch over a JMP which
  immediately reboots (so, this code is
  very intolerant of errors -- and very
  unfriendly!)

- another subroutine with a reference
  to "$C089,X" (a common way to turn on
  the drive motor manually)

- that subroutine also contains several
  instances of "$C08C,X" (to read the
  data latch of the drive)

It appears this code is loaded into
memory at $3000. Here it is through the
eyes of a sector editor:

                 --v--

T10,S09
----------- DISASSEMBLY MODE ----------
; set up some parameters
0000:A9 40          LDA   #$40
0002:8D 87 30       STA   $3087
0005:A9 00          LDA   #$00
0007:8D 86 30       STA   $3086
000A:8D 88 30       STA   $3088
000D:8D 89 30       STA   $3089

; get slot number x16 (standard ProDOS
; location)
0010:AD 30 BF       LDA   $BF30
0013:8D 85 30       STA   $3085

0016:20 68 30       JSR   $3068
.
.
.
; call ProDOS MLI
0068:20 00 BF       JSR   $BF00

; MLI command=#$80 (raw block read)
006B:80

; address of parameter table (set up
; earlier to point to block #$0000 into
; $4000)
006C:84 30

; immediately return regardless of
; success or failure
006E:60             RTS

Continuing from $3019...

; Z flag set -> branch over next line
0019:F0 03          BEQ   $001E

; if Z flag is clear, there was some
; error with the ProDOS MLI call, so we
; immediately reboot
001B:4C E7 30       JMP   $30E7
.
.
.
00E7:4C 00 C6       JMP   $C600

Continuing from $301E...

; get slot number x16 again
001E:AE 30 BF       LDX   $BF30

; call subroutine
0021:20 9A 30       JSR   $309A

; and store result (!)
0024:85 1A          STA   $1A
.
.
.
; turn on drive motor manually
009A:BD 89 C0       LDA   $C089,X

; some sort of Death Counter
009D:A9 56          LDA   #$56
009F:85 1C          STA   $1C
00A1:A9 08          LDA   #$08
00A3:C6 1B          DEC   $1B
00A5:D0 04          BNE   $00AB
00A7:C6 1C          DEC   $1C

; If the Death Counter hits zero, that
; would be bad. Which part of "Death
; Counter" sounded good to you, anyway?
; (Specifically, this jumps to the
; immediate reboot we saw earlier)
00A9:F0 3C          BEQ   $00E7

; look for an #$FB nibble
00AB:BC 8C C0       LDY   $C08C,X
00AE:10 FB          BPL   $00AB
00B0:C0 FB          CPY   #$FB
00B2:D0 ED          BNE   $00A1
00B4:F0 00          BEQ   $00B6

; kill a few cycles (not pointless,
; because the disk spins independently
; of the CPU, so all of these low-level
; disk reads are highly time-sensitive)
00B6:EA             NOP
00B7:EA             NOP

; read data latch (note: no BPL loop
; here, we're just reading it once)
00B8:BC 8C C0       LDY   $C08C,X

; do a compare to set or clear the
; carry bit (among other things, but
; it's the carry bit we care about)
00BB:C0 08          CPY   #$08

; rotate the carry into the low bit of
; the accumulator
00BD:2A             ROL

; if we just rolled a "1" bit out of
; the high bit of the accumulator, take
; this branch
00BE:B0 0B          BCS   $00CB

; next nibble needs to be #$FF
00C0:BC 8C C0       LDY   $C08C,X
00C3:10 FB          BPL   $00C0

; ...otherwise we start over
00C5:C0 FF          CPY   #$FF
00C7:D0 D8          BNE   $00A1

; loop back to get next nibble
00C9:F0 EB          BEQ   $00B6

; execution continues here (from $00BE)
; get another nibble
00CB:BC 8C C0       LDY   $C08C,X
00CE:10 FB          BPL   $00CB

; stash it in zero page
00D0:84 1B          STY   $1B

; if the accumulator is anything but
; %00001010, start over
00D2:C9 0A          CMP   #$0A
00D4:D0 CB          BNE   $00A1

; get one more nibble
00D6:BD 8C C0       LDA   $C08C,X
00D9:10 FB          BPL   $00D6

; more bit twiddling
00DB:38             SEC
00DC:2A             ROL

; AND it with the previously stashed
; nibble
00DD:25 1B          AND   $1B
00DF:49 FF          EOR   #$FF

; branch on failure
00E1:F0 07          BEQ   $00EA

; turn off drive motor manually
00E3:DD 88 C0       CMP   $C088,X

; and exit gracefully to the caller
00E6:60             RTS

I got lost several times trying to
follow this routine. I think the
easiest way to explain it is to show
the difference between the original
disk and my non-working copy.

                   ~

               Chapter 2
        It's Time To Get Visual


Here is the original disk, as seen by
the Copy II+ nibble editor. Nibbles
with extra "0" bits (timing bits) after
them are displayed in inverse on an
original machine, marked here with a
"+" after the nibble.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1B1E  LENGTH: 17C1

1C70: 9F EB E5 FC D7 D7 D7 EE   VIEW
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF+FF FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA
---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

It's easy to understand why a simple
sector copy failed. The sequence that
this code is looking for starts at
offset $1C93, which is between the end
of one sector and the beginning of the
next. (The data epilogue is at $1C8C;
the next address prologue is at $1CA2.)
Sector copiers discard everything
between those delimiters and rebuild
the track with a default pattern of
sync bytes. That pattern doesn't
include an $FB nibble, so the nibble
check fails.

But the EDD bit copy also failed. Here
is the original disk's pattern at
offset $1C93:

  - #$FB + timing bit
  - #$FF
  - #$FF + timing bit
  - #$FF
  - #$FF + timing bit

And here is what the same part of the
track looks like on my failed EDD copy:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1B1E  LENGTH: 17C1

1C70: 9F EB E5 FC D7 D7 D7 EE   VIEW
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF FF+FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA
---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

A subtle difference! The sequence at
offset $1C93 now looks like this:

  - #$FB + timing bit
  - #$FF
  - #$FF
  - #$FF + timing bit
  - #$FF + timing bit

This code is looking for #$FF nibbles
with an alternating pattern of timing
bit, no timing bit, timing bit, no
timing bit. It doesn't find that on the
bit copy, so it knows it's not running
on an original disk.

                   ~

               Chapter 3
      That's What Friends Are For


This protection check has side effects;
it sets the accumulator to the value of
some bit math performed on two nibbles
read from disk. I don't know what those
values are, so I don't know what the
accumulator is when the protection
routine returns to the caller, so I
don't know what value ends up in zero
page $1A.

I also don't know if it matters or not,
but I'm going to err on the side of
caution and assume that it does. I have
seen this protection check before, but
not this variation that stores the
final value of the accumulator.

So... how to capture it? I could boot
trace the entire disk, but that's...
boring. I could sector-edit the code on
the original disk, but NEVER DO THIS
DON'T EVEN ASK WHY JUST NO. Instead,
I'm going to take advantage of the fact
that I have two disk drives, and that
this protection check takes a slot (and
drive) on entry, and I'm going to hack
a copy.

[S6,D1=original disk]
[S5,D1=non-working copy]

Editing the NON-WORKING COPY with the
Disk Fixer sector editor, I can make
two small changes to this sector:

; Instead of getting the current slot+
; drive from ProDOS, hard-code it to
; slot 6, drive 1. Do this before each
; JSR (the raw block read and the
; protection check itself)
T10,S09,$10: AD30BF -> EAA960
T10,S09,$1E: AE30BF -> EAA260

]PR#5
...reboots slot 5...
...briefly accesses slot 6...
...continues booting from slot 5...

Hooray! We have a bootable copy... as
long as the original disk is in slot 6.

One more edit (TO THE COPY OF COURSE),
to print out the accumulator after the
protection check passes:

T10,S09,$24: 851AA00BB95C ->
             20DAFD4C69FF

]PR#5
...reboots slot 5...
...briefly accesses slot 6...
03
<beep>

Excellent. The value of the accumulator
on exit is #$03.

Starting over with a fresh (but non-
working) copy of the original disk, I
can make a small edit to the protection
routine so it sets the accumulator to
#$03 and exits unconditionally.

[S6,D1=fresh non-working copy]

T10,S09,$9A: BD89C0 -> A90360

]PR#6
...works...

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 684
------------------EOF------------------
